package main

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
)

/*
	编写学生结构体 实现学生的增删改查 在第一版的基础上新增将结果保存到文件中、同时进行序列化和反序列操作
*/
//定义学生结构体
type Students struct {
	Id    int      `json:"id"`
	Name  string   `json:"name"`
	Age   int      `json:"age"`
	Class []string `json:"class"`
}

// NewStudent 构造学生结构体的函数
func NewStudent(id int, name string, age int, class []string) *Students {
	return &Students{
		Id:    id,
		Name:  name,
		Age:   age,
		Class: class,
	}
}

// StuMgr 定义一个学生管理者结构体，用来保存学生数据
type StuMgr struct {
	allSts   []map[string]*Students //用来保存学生数据
	dataFile string                 //定义保存数据文件的名字
}

// NewStuMgr 构造学生管理器的结构体函数
func NewStuMgr(dataFile string) *StuMgr {
	return &StuMgr{
		allSts:   make([]map[string]*Students, 0), //初始化存储数据的空间
		dataFile: dataFile,
	}

}

// Viewallsts 查看全部学生信息
/*
	因为学生信息都保存在学生管理器的结构体中,想要查看学生信息需要一个接收者,这个接收者s *StuMgr返回的是一个经过make初始化的存储空间
	因为是一个key为string类型、value为*Student的切片类型,因此查看学生信息可以通过 切片类型的for循环拿到对应的value，这个value就是map类型
然后这个循环这个value,打印出values为*Student保存的值
*/
func (s *StuMgr) Viewallsts() {
	fmt.Println("查看全部学生信息")
	//从s.allSts中把数据拿出来并打印

	for _, value := range s.allSts {
		for _, values := range value {
			fmt.Println(values.Id, values.Name, values.Age, values.Class)
		}
	}
}

// Viewoncests 查看单个学生信息
/*
	因为学生信息都保存在学生管理器的结构体中,想要查看学生信息需要一个接收者,这个接收者s *StuMgr返回的是一个经过make初始化的存储空间
该函数接收一个传递进来的int参数,通过输入的id与值为*Student类型中的id对比,如果相等,则打印出这个id对应的用户信息
	接收者表示的是调用该方法的具体类型变量
*/
func (s *StuMgr) Viewoncests(id int) (ss bool) {
	fmt.Println("查看单个学生信息")
	for _, value := range s.allSts {
		for _, values := range value {
			if values.Id == id {
				fmt.Println(values.Id, values.Name, values.Age, values.Class)
				return true
			}
		}
	}
	return true
}

// AddMsgsts  增加学生信息
/*
	该函数接收一个*Students类型的参数 最后把增加的信息通过接收者s *StuMgr 保存到StuMgr结构体中
先是循环切片,拿个每个索引对应的值，该值是map类型。然后在通过map的ok判断方法，检查存储空间中是否有已经存在的键所对应的值，如果有则直接eturn false
如果存储空间中没有，则使用make初始化一个map类型，将传进来的形参保存到这个map类型中,再将这个map通过切片的append方法追加到存储空间中,实现增加功能
*/
func (s *StuMgr) AddMsgsts(student *Students) (ss bool) {
	fmt.Println("增加学生信息")
	for _, value := range s.allSts {
		if _, ok := value[student.Name]; ok { //根据存储的Name字段判断增加学生是否有重名，如果有则直接返回false
			return false
		}
	}
	stsmsg := make(map[string]*Students)
	stsmsg[student.Name] = student
	s.allSts = append(s.allSts, stsmsg)
	s.saveToFile()
	return true
}

// UpdateMsgsts 更新函数
/*
	通过传递的形参id,判断想要更新的信息是否存在,先是循环切片，拿到map 再通过循环map，进行传入的id与保存的id进行对比，如果相等,则调用另一个形参student
进行map类型的增加操作，将旧值修改为新值 最后将结果再次写入到持久化数据文件中
*/
func (s *StuMgr) UpdateMsgsts(id int, student *Students) bool {
	fmt.Println("更新学生信息")
	for _, values := range s.allSts {
		for _, value := range values {
			if value.Id == id {
				value.Id = student.Id
				value.Name = student.Name
				value.Age = student.Age
				value.Class = student.Class
				s.saveToFile()
				return true
			} else {
				fmt.Println("传参id未与map中的id相匹配")
				return false
			}
		}
	}
	return true
}

// DelMsgsts 删除学生信息
/*
	通过传入的形参id判断对应的信息是否存在，先进行切片循环,拿到对应的map 在进行map循环操作,判断*Student值中是否有与形参id一样的值，如果有，则进行
删除map操作，删除后,再进行判断map的长度是否为0了，如果确实是0，则进行切片的删除操作，将切片中的索引进行删除即可
*/
func (s *StuMgr) DelMsgsts(id int) bool {
	fmt.Println("删除学生信息")
	for index, value := range s.allSts { //先是循环切片
		for key, values := range value { //在循环从切片中获得的map
			if values.Id == id { //map中的Id值等于传参的id时  删除这个切片索引对应的map
				delete(value, key)
				//fmt.Println("删除key后", values)
				if len(value) == 0 { //当索引对应的map的长度是0时，在通过append切片的方法将空map删掉
					s.allSts = append(s.allSts[:index], s.allSts[index+1:]...)
					s.saveToFile() //将最后的结果再次写入到持久化数据文件中
				}
				return true
			}
		}
	}
	return false
}

// 序列化 将数据保存到本地文件中
func (s *StuMgr) saveToFile() error {
	// 先判断文件是否存在，如果不存在，则通过os.Create创建文件
	if _, err := os.Stat(s.dataFile); os.IsNotExist(err) {
		os.Create(s.dataFile)
	}
	// 序列化 保存数据的切片 因为 s.allSts 的类型是[]mao[string]*Student
	data, err := json.Marshal(s.allSts)
	if err != nil {
		return err
	}
	//序列化成功后,通过ioutil.WriteFile函数将序列化后的数据以json格式保存到创建的文件中
	err = ioutil.WriteFile(s.dataFile, data, 0644)
	if err != nil {
		return err
	}
	return nil
}

// 反序列化
func (s *StuMgr) readdfile() (err error) {
	// 先判断文件是否存在，如果不存在，则通过os.Create创建文件  因为ioutil这个包在进行文件操作时是不需要打开和关闭文件的
	if _, err := os.Stat(s.dataFile); os.IsNotExist(err) {
		os.Create(s.dataFile)
	}
	// 通过ioutil.ReadFile读取保存序列化数据的文件
	data, err := ioutil.ReadFile(s.dataFile)
	if err != nil {
		return err
	}
	//再将这个序列化json文件通过Unmarshal进行反序列化操作，这样就能通过循环s.allSts 打印出对应的数据
	if err := json.Unmarshal(data, &s.allSts); err != nil {
		return err
	}
	fmt.Println(s.allSts) //[map[ops:0xc00002a0c0] map[dev:0xc00002a180]] 反序列化后的结果是这样的，0xc00002a0c0这种对应的就是存Students的地址
	return nil
}
